深度学习训练营课程笔记。
References
深度学习 Tensorflow 相关书籍推荐和 PDF 下载
逻辑回归
逻辑回归的内容可以回顾这里:机器学习初等指南 (1)- 逻辑回归。
Ex:利用年龄和收入预测是否买车。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | from sklearn import linear_model #引入sklearn线性模型 X = [[20, 3], [23, 7], [31, 10], [42, 13], [50, 7], [60, 5]] #年龄-收入矩阵 y = [0, 1, 1, 1 ,0 , 0] #观测向量 lr = linear_model.LogisticRegression() #创建对象 lr.fit(X,y) #获得拟合参数 testX = [[28, 8]] #测试集(矩阵) label = lr.predict(testX) #预测 print("predicted Label = ", label) prob = lr.predict_proba(testX) #预测概率 print("probability = ", prob) |
拟合系数的含义
𝑃(𝑌=1|𝑥;𝜃)=𝑓(𝑥;𝜃)=11+𝑒−𝜃𝑇𝑥P(Y=1|x;θ)=f(x;θ)=11+e−θTx P(Y=1|x;\theta)=f(x;\theta)=\cfrac{1}{1+e^{-\theta^Tx}}
则概率比值𝑜𝑑𝑑𝑠=𝑝1−𝑝=𝑒𝜃𝑇𝑥odds=p1−p=eθTxodds=\frac{p}{1-p}=e^{\theta^Tx}。
系数 𝜃𝑗 意味着,假设 𝑜𝑑𝑑𝑠 为 𝜆1(原),𝜆2(新),若 𝑥𝑗 增加 1,有 𝜆2𝜆1=𝑒𝜃𝑗 系数 θj 意味着,假设 odds 为 λ1(原),λ2(新),若 xj 增加 1,有 λ2λ1=eθj 系数\theta_j 意味着,假设 odds 为\lambda_1(原),\lambda_2(新),若 x_j 增加 1,有\cfrac{\lambda_2}{\lambda_1}=e^{\theta_j}。
以下是验证程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | from sklearn import linear_model #引入sklearn线性模型 X = [[20, 3], [23, 7], [31, 10], [42, 13], [50, 7], [60, 5]] #年龄-收入矩阵 y = [0, 1, 1, 1 ,0 , 0] #观测向量 lr = linear_model.LogisticRegression() lr.fit(X,y) #获得拟合参数 testX = [[28, 8]] #测试集(矩阵) label = lr.predict(testX) #预测 print("predicted Label = ", label) prob = lr.predict*proba(testX) #预测概率 print("probability = ", prob) #-----------------------------------------以下是新增部分 theta_0 = lr.intercept* theta*1 = lr.coef*[0][0] theta*2 = lr.coef*[0][1] print("theta_0 = ", theta_0) print("theta_1 = ", theta_1) print("theta_2 = ", theta_2) ratio = prob[0][1]/prob[0][0] testX = [[28, 9]] prob_new = lr.predict_proba(testX) ratio_new = prob_new[0][1]/prob_new[0][0] ratio_of_ratio = ratio_new / ratio print("ratio_of_ratio = ", ratio_of_ratio) import math theta_2_exp = math.exp(theta_2) print("theta_2_exp = ", theta_2_exp) |
应用案例
参考程序:spam_detection.ipynb, 垃圾短信检测数据集. zip
示例代码
在 hexo 中写的文章支持 jupyter-notebook 显示 | 幻悠尘的小窝
注意需要
npm install co
。太偏右修正:https://github.com/qiliux/hexo-jupyter-notebook/issues/3。
核心代码:
document.getElementById('ipynb').style['margin-left'] = '-60px';
简单代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import pandas as pd from sklearn import linear_model from sklearn.feature_extraction.text import TfidfVectorizer df = pd.read_csv('spam.csv', delimiter=',', header=None, encoding='latin-1') print(df.head(5)) y, X_train = df[0], df[1] # df = pd.read_csv('spam.csv', delimiter=',', encoding='latin-1') # y, X_train = df.iloc[:,0], df.iloc[:,1] vectorizer = TfidfVectorizer() X = vectorizer.fit_transform(X_train) lr = linear_model.LogisticRegression() lr.fit(X, y) testX = vectorizer.transform(['URGENT! Your mobile No. 1234 was awarded a prize.', 'Hey honey, whats up?']) predictions = lr.predict(testX) print(predictions) |
神经网络
神经网络的内容可以回顾这里:机器学习初等指南 (2)。
鸢尾花分类
https://raw.githubusercontent.com/uiuc-cse/data-fa14/gh-pages/data/iris.csv
【python 数据挖掘课程】十九. 鸢尾花数据集可视化、线性回归、决策树花样分析
Iris 鸢尾花数据集可视化、线性回归、决策树分析、KMeans 聚类分析
Iris plants 数据集可以从
KEEL dataset
数据集网站获取,也可以直接从Sklearn.datasets
机器学习包得到。数据集共包含 4 个特征变量、1 个类别变量,共有 150 个样本。类别变量分别对应鸢尾花的三个亚属,分别是山鸢尾 (Iris-setosa)
、变色鸢尾(Iris-versicolor)
和维吉尼亚鸢尾(Iris-virginica)
分别用[0,1,2]
来做映射。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | # -*- coding: utf-8 -*- import numpy as np import pandas as pd from keras.models import Sequential #序列串行类 from keras.layers import Dense #密集层(全连接神经网络) from keras.wrappers.scikit_learn import KerasClassifier #与sklearn的包装接口 from keras.utils import np_utils # from sklearn.model_selection import cross_val_score # 交叉验证准确度得分 from sklearn.model_selection import KFold # k折交叉验证 from sklearn.preprocessing import LabelEncoder # 标签编码(转化为数值,类似向量化) from keras.models import model_from_json # 训练模型的储存 # reproducibility seed = 13 # 伪随机数种子 np.random.seed(seed) # load data df = pd.read_csv('iris.csv') # from sklearn.datasets import load_iris # df = load_iris() X = df.values[:,0:4].astype(float) #设定为单精度浮点类型 Y = df.values[:,4] encoder = LabelEncoder() #新建编码器 Y_encoded = encoder.fit_transform(Y) #进行数值转换 Y_onehot = np_utils.to_categorical(Y_encoded) #转化为向量 #define a network def baseline_model(): model = Sequential() model.add(Dense(7, input_dim=4, activation='tanh')) #新建(隐藏)层:Dense 全连接,7 个神经元,输入维度为 4,激活函数为 tanh 双曲正切 model.add(Dense(3, activation='softmax')) #新建输出层,3 个输出,激活函数为 softmax model.compile(loss='mean_squared_error', optimizer='sgd', metrics=['accuracy']) #损失函数:最小均方。优化方法:随机梯度下降。评估指标:准确度 return model estimator = KerasClassifier(build_fn=baseline_model, epochs=20, batch_size=1, verbose=1) # 交叉验证。 build_fn:模型生成方法。epochs:训练次数。batchsize:批处理容量。verbose:输出信息详细度。 # epchs 如果增加到 200,准确率可达 97%。 #evalute kfold = KFold(n_splits=10, shuffle=True, random_state=seed) # n_split:将数据集分成 10 份。shuffle:乱序预处理。 result = cross_val_score(estimator, X, Y_onehot, cv=kfold) print("Accuray of cross validation, mean %.2f,std %.2f" % (result.mean(), result.std())) #均值、方差 #save model estimator.fit(X, Y_onehot) model_json = estimator.model.to_json() with open("model.json", "w") as json_file: # 保存结构 json_file.write(model_json) estimator.model.save_weights("model.h5") # 保存数值 print("save model to disk") # load model and use it for prediction json_file = open("model.json", "r") loaded_model_json = json_file.read() json_file.close() loaded_model = model_from_json(loaded_model_json) loaded_model.load_weights("model.h5") print("loaded model from dish") predicted = loaded_model.predict(X) print("predicted probability:" + str(predicted)) predicted_label = loaded_model.predict_classes(X) print("predicted label:" + str(predicted_label)) |
一些知识
softmax
存在数值问题。(中间有指数膨胀)
可以利用最大值偏置进行修正。避免溢出。
tanh
函数:
深度神经网络
深度神经网络的两大挑战:
梯度消亡(Gradient Vanishing):训练过程非常慢。
过拟合(Overfitting):在训练数据上效果好,在测试数据集上效果差。
梯度消亡
神经网络靠近输入端的网络层系数变化不敏感。当网络层数增加时,现象更明显。
梯度消亡的前提:
- 使用基于梯度的训练方法
- 激活函数的输出值范围远小于输入值范围(
sigmoid
、tanh
、softmax
)
实际上,对于这样的激活函数,100、10000 和 10005 三者的激活值几乎一致。
如果一个(大)系数的微小变化对网络的影响很小,那么就很难进行优化(优化特别慢)。训练起来就很困难了。
可以想象的是,损失函数是一个非常平坦的凹面。
ReLU 激活
ReLU
:𝑓(𝑥)=𝑚𝑎𝑥(0,𝑥)f(x)=max(0,x)f(x)=max(0,x)。- 正值梯度。
LeakyReLU
:𝑓(𝑥)=𝑚𝑎𝑥(𝑎𝑥,𝑥)f(x)=max(ax,x)f(x)=max(ax,x)。- 优化了负值梯度。
为什么不直接选择激活函数 𝑓(𝑥)=𝑥f(x)=xf(x)=x(恒同映射)?
激活函数一定要是非线性的。如果激活函数是线性函数,那么最后得到的就是一个线性分类器。
激活函数的非线性越强,那么分类能力也就越强。
非梯度训练方法
过拟合
解决方案:
Dropout
L2
正则化L1
正则化MaxNorm
Dropout
Dropout: A Simple Way to Prevent Neural Networks from Overfitting
【论文精读】Dropout: A Simple Way to Prevent Neural Networks from …,Atlas8346
Dropout 可以被解释为一种通过在隐藏的单元中添加噪声来调节神经网络的方法。
大概意思就是引入神经元的休息状态。神经元有一定概率被Dropout
,也就完全不能被激活,从而输出全为0
。
L2
正则化
L1
正则化
MaxNorm
神经网络系数的初始化
实战项目 1: 自动为图片生成描述 Image Captioning
环境安装参考:机器学习初等指南 (1)- 环境安装与使用
Task1 构建 VGG16
经典的网络有:VGG
, ResNet
, DenseNet
。
深度学习卷积神经网络——经典网络 VGG-16 网络的搭建与实现 …
VGGNet 探索了卷积神经网络的深度与其性能之间的关系,通过反复堆叠 3 3 的小型卷积核和 2 2 的最大池化层,VGGNet 成功地构筑了 16~19 层深的卷积神经网络。VGGNet 相比之前 state-of-the-art 的网络结构,错误率大幅下降, VGGNet 论文中全部使用了 3 3 的小型卷积核和 2 2 的最大池化核,通过不断加深网络结构来提升性能。
简单来说,在 VGG 中,使用了 3 个 3x3 卷积核来代替 7x7 卷积核,使用了 2 个 3x3 卷积核来代替 5x5 卷积核,这样做的主要目的是在保证具有相同感知野的条件下,提升了网络的深度,在一定程度上提升了神经网络的效果。
Pre - 卷积
A Comprehensive Introduction to Different Types of Convolutions in Deep Learning
如何理解 Graph Convolutional Network(GCN)?
深度学习笔记 5:池化层的实现
max-pooling
和mean-pooling
卷积层与池化层 卷积向下取整,池化向上取整。
例题:(Here)
- 输入图片大小为 200×200,依次经过一层卷积(kernel size 5×5,padding 1,stride 2),pooling(kernel size 3×3,padding 0,stride 1),又一层卷积(kernel size 3×3,padding 1,stride 1)之后,输出特征图大小为?
答案:97。
公式:
𝑓=(𝑚𝑎𝑝𝑠𝑖𝑧𝑒−𝑘𝑒𝑟𝑛𝑒𝑙𝑠𝑖𝑧𝑒+2×𝑝𝑎𝑑𝑑𝑖𝑛𝑔)𝑠𝑡𝑟𝑖𝑑𝑒+1f=(mapsize−kernelsize+2×padding)stride+1 f=\cfrac{(map{size}-kernel{size}+2\times padding)}{stride}+1
卷积层 1:
(input_size - kernel_size + 2*padding)/stride + 1
=(200-5+2*1)/2+1 $$\longrightarrow$$floor(99.5)
=99
池化层:
(99-3)/1+1 $$\longrightarrow$$ceil(97)
=97
卷积层 2:
(97-3+2*1)/1+1 $$\longrightarrow$$floor(97)
=97
全连接层(fully connected layers,FC)在整个卷积神经网络中起到 “分类器” 的作用。在实际使用中,全连接层可由卷积操作实现:对前层是全连接的全连接层可以转化为卷积核为
1x1
的卷积;而前层是卷积层的全连接层可以转化为卷积核为hxw
的全局卷积,h 和 w 分别为前层卷积结果的高和宽。以 VGG-16 为例,对 224x224x3 的输入,最后一层卷积可得输出为 7x7x512,如后层是一层含 4096 个神经元的 FC,则可用卷积核为 7x7x512x4096 的全局卷积来实现这一全连接运算过程,其中该卷积核参数如下:
“filter size = 7, padding = 0, stride = 1, D_in = 512, D_out = 4096”
经过此卷积操作后可得输出为 1x1x4096。
如需再次叠加一个 2048 的 FC,则可设定参数为 “filter size = 1, padding = 0, stride = 1, D_in = 4096, D_out = 2048” 的卷积层操作。
目前由于全连接层参数冗余(仅全连接层参数就可占整个网络参数 80% 左右),近期一些性能优异的网络模型如 ResNet 和 GoogLeNet 等均用全局平均池化(global average pooling,GAP)取代 FC 来融合学到的深度特征,最后仍用 softmax 等损失函数作为网络目标函数来指导学习过程。需要指出的是,用 GAP 替代 FC 的网络通常有较好的预测性能。
微调(fine tuning)是深度学习领域最常用的迁移学习技术。
1x1 卷积一般只改变输出通道数(channels),而不改变输出的宽度和高度。
卷积:一个函数经过区间镜像翻转和平移后与另一个函数的乘积的积分。
(𝑓∗𝑔)(𝑡)=∫∞−∞𝑓(𝜏)𝑔(𝑡−𝜏)𝑑𝜏(f∗g)(t)=∫−∞∞f(τ)g(t−τ)dτ (f*g)(t)=\int_{-\infty}^{\infty}f(\tau)g(t-\tau)d\tau
Source: http://fourier.eng.hmc.edu/e161/lectures/convolution/index.html
在深度学习中,卷积中的过滤函数是不经过翻转的。
深度卷积:一个函数f
经过平移后与另一个函数g
的乘积的积分。深度卷积即互关联(Cross-correlation)。
过滤函数:函数 g
称为一个过滤函数。
执行卷积的目的就是从输入中提取有用的特征。
真子空间:一个空间若真包含于
另一个空间,那么它就是另一个空间的子空间。
张量空间:一个空间如果是以张量(多维离散立方体)的形式定义的,就叫张量空间。
一张图像就是张量空间(矩阵空间)上的一个点,也就是张量。
窗口:一个真子空间称为原空间的一个窗口。
通道(channel):如果过滤函数g
在张量空间中有一个窗口,在这个窗口外g
恒零,那么称这个窗口叫g
的通道。
卷积核(kernel):给定张量空间,一个存在通道的过滤函数g
,称为张量空间上的一个卷积核。
卷积核本身就是张量。一般而言,定义卷积核同时就应该明确指定所使用的通道。
在深度学习中,卷积就是元素级别( element-wise) 的乘法和加法。
对于一张具有单通道(单卷积核)的图像,卷积过程如上图所示,过滤函数是一个组成部分为
[[0, 1, 2], [2, 2, 0], [0, 1, 2]]
的 3 x 3 矩阵,它滑动穿过整个输入。在每一个位置,它都执行了元素级别的乘法和加法,而每个滑过的位置都得出一个数字,最终的输出就是一个 3 x 3 矩阵。(注意:在这个示例中,卷积步长=1
;填充=0
。)
滑动卷积:过滤函数不变,卷积核遍历所有相同规模的窗口,上图所示的过程就是滑动卷积。
如果按照一定步长迭代,并非遍历的话,也可以有类似的讨论。叫做广义滑动卷积。
覆盖卷积:如果张量上定义若干个卷积核,这些卷积核的通道构成张量空间的一个覆盖>),则这些卷积核可覆盖卷积。
滑动卷积是覆盖卷积的特例。
插一句,有限覆盖定理。(虽然没什么关系~)
卷积映射(卷积层):一个张量被若干卷积核覆盖卷积,获得新张量,则称为卷积映射。
卷积映射函数即过滤器(filter)。有时过滤器也指若干卷积核堆积形成的张量。
一个卷积映射就对应一个卷积层。
通过卷积映射,原张量被嵌入到一个相对规模更小的张量空间中。
卷积核可以不一样哦。不一定等效于滑动卷积了。
卷积核大小(Kernel size):卷积核的窗口的大小。
卷积步长(Stride):卷积核滑动通过图像的步长。
填充(Padding):填充定义如何处理图像的边界。
- 空填充(将输入边界周围的填充设置为 0,
padding='same'
)
- 不填充(映射后张量变小,
padding='valid'
)
Tensorflow 中 padding 的两种类型 SAME 和 VALID
A guide to convolution arithmetic for deep learning (《深度学习的卷积算法指南》,详细介绍各种卷积核操作)
多通道卷积:如果覆盖卷积中任意一个卷积核都构成滑动卷积,那么称为多通道卷积。
下面是一个 3 个卷积核的滑动卷积(RGB 位图)。一个图像可以是多层的,滑动卷积只需要在某一层上滑动。
Source: https://towardsdatascience.com/intuitively-understanding-convolutions-for-deep-learning-1f6f42faee1
之后,这 3 个通道都合并到一起(元素级别的加法)组成了一个大小为
3 x 3 x 1
的单通道。
这个通道是输入层(5 x 5 x 3 矩阵)使用了过滤器(3 x 3 x 3
矩阵)后得到的结果。
更广义的如下图:(紫色部分的长度就是通道数目,全部合并)
这其实就是高维卷积核(过滤器)。
如果通道不发生合并:(
Din=Dout
)
以及推广的高维卷积核,窗口可以在多个维上滑动:(
Din>Dout
)
在执行计算昂贵的 3 x 3 卷积和 5 x 5 卷积前,往往会使用 1 x 1 卷积来减少计算量。
1 x 1 卷积最初是在 Network-in-network 的论文中被提出的,之后在谷歌的 Inception 论文中被大量使用。
转置卷积:卷积映射的对应的张量膨胀的卷积称为一个转置卷积。
如果转置卷积映射存在逆映射,则称为逆卷积。
转置卷积的原理如下图所示:
转置的含义来自上述原理中的矩阵的转置。而逆来自逆映射。
空洞卷积(Dilated Convolution):在卷积核部分之间插入空间让卷积核膨胀。即扩张卷积。
类似海绵。本质上是在不增加额外的计算成本的情况下增加感受野。
《使用深度卷积网络和全连接 CRF 做语义图像分割》(Semantic Image Segmentation with Deep Convolutional Nets and Fully Connected CRFs,https://arxiv.org/abs/1412.7062)
《通过空洞卷积做多规模的上下文聚合》(Multi-scale context aggregation by dilated convolutions,https://arxiv.org/abs/1511.07122)
还有可分离卷积、扁平化卷积、分组卷积等。(详见 Here)
VGG 引述
VGG 是由 Simonyan 和 Zisserman 在文献《Very Deep Convolutional Networks for Large Scale Image Recognition》(PDF)中提出卷积神经网络模型,其名称来源于作者所在的牛津大学视觉几何组 (Visual Geometry Group) 的缩写。
该模型参加 2014 年的 ImageNet 图像分类与定位挑战赛,取得了优异成绩:
- 在分类任务上排名第二,在定位任务上排名第一。
VGG 分类
VGG 中根据卷积核大小和卷积层数目的不同,可分为A
,A-LRN
,B
,C
,D
,E
共 6 个配置 (ConvNet Configuration),其中以D
,E
两种配置较为常用,分别称为VGG16
和VGG19
。
对 VGG16 进行具体分析发现,
VGG16
共包含:
- 13 个卷积层(Convolutional Layer),分别用
conv3-XXX
表示- 3 个全连接层(Fully connected Layer), 分别用
FC-XXXX
表示- 5 个池化层(Pool layer), 分别用
maxpool
表示其中,卷积层和全连接层具有权重系数,因此也被称为
权重层
,总数目为13+3=16
,这即是 VGG16 中 16 的来源。(池化层不涉及权重,因此不属于权重层,不被计数)。
VGG 的优点
VGG16 的突出特点是简单,体现在:
- 卷积层均采用相同的卷积核参数:
- 卷积层均表示为
conv3-XXX
,其中conv3
说明该卷积层采用的卷积核的尺寸 (kernel size) 是 3,即宽(width)和高(height)均为 3,3*3
是很小的卷积核尺寸,结合其它参数(步幅stride=1
,填充方式padding=same
),这样就能够使得每一个卷积层 (张量) 与前一层(张量)保持相同的宽和高。XXX
代表卷积层的通道数。
- 卷积层均表示为
- 池化层均采用相同的池化核参数池化层的参数均为 2×2,步幅
stride=2
,max 的池化方式,这样就能够使得每一个池化层(张量)的宽和高是前一层(张量)的 1/2。 - 模型是由若干卷积层和池化层堆叠(stack)的方式构成,比较容易形成较深的网络结构(在 2014 年,16 层已经被认为很深了)。
综合上述分析,可以概括 VGG 的优点为: Small filters, Deeper networks
VGG 的缺点
训练时间过长,调参难度大。
存储容量大,不利于部署。(存储 VGG16 权重值文件的大小为 500 多 MB)
块(Block)
VGG16 的卷积层和池化层可以划分为不同的块(Block),从前到后依次编号为 Block1~block5。
- 每一个块内包含若干卷积层和一个池化层。
- 同一块内,卷积层的通道(channel)数是相同的。
block3
: 3 个卷积层,卷积核尺寸为3*3
,通道数都是256
随着层数的增加:
- 卷积通道数翻倍:64→128→256→512(到 512 不再增加了)
- 张量尺寸减半:224→ 112→ 56→28→ 14→ 7(池化层)
VGG 参数
VGG 参数包括卷积核参数和全连接层参数。两者都需要学习得到。
- 卷积核参数
- 对于第一层卷积,由于输入图的通道数是 3(RGB),网络必须学习大小为 3×3,通道数为 3 的的卷积核,这样的卷积核有 64 个,因此总共有(3×3 × 3)× 64 = 1728 个参数。
- 全连接层参数
- =
前一层节点数
×本层的节点数
。
- =
- 最大池化层没有参数
FeiFei Li 在 CS231 的课件中给出了整个网络的全部参数(138 357 544 个参数)的计算过程(不考虑偏置),如下图所示,图中红色是计算所需存储容量的部分;蓝色是计算权重参数数量的部分:(Lecture 9: CNN Architectures)
VGG16 构建代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | from keras.models import Sequential from keras.layers import Dense, Flatten from keras.layers import Conv2D from keras.layers import MaxPooling2D def generate_vgg16(): input_shape = (224, 224, 3) # 输入: 224*244,RGB 三位图 model = Sequential([ Conv2D(64, (3, 3), input_shape=input_shape, padding='same', activation='relu'), # 卷积层,64 个滤波器(卷积核),尺寸 3*3,参数:输入规格,填充,激活函数 Conv2D(64, (3, 3), padding='same', activation='relu'), # 非首层无需指定输入规格 MaxPooling2D(pool_size=(2, 2), strides=(2, 2)), # Block 2 Conv2D(128, (3, 3), padding='same', activation='relu'), Conv2D(128, (3, 3), padding='same', activation='relu'), MaxPooling2D(pool_size=(2, 2), strides=(2, 2)), # 3 Conv2D(256, (3, 3), padding='same', activation='relu'), Conv2D(256, (3, 3), padding='same', activation='relu'), Conv2D(256, (3, 3), padding='same', activation='relu'), MaxPooling2D(pool_size=(2, 2), strides=(2, 2)), # 4 Conv2D(512, (3, 3), padding='same', activation='relu'), Conv2D(512, (3, 3), padding='same', activation='relu'), Conv2D(512, (3, 3), padding='same', activation='relu'), MaxPooling2D(pool_size=(2, 2), strides=(2, 2)), # 5 Conv2D(512, (3, 3), padding='same', activation='relu'), Conv2D(512, (3, 3), padding='same', activation='relu'), Conv2D(512, (3, 3), padding='same', activation='relu'), MaxPooling2D(pool_size=(2, 2), strides=(2, 2)), # 全连接层 Flatten(), Dense(4096, activation='relu'), Dense(4096, activation='relu'), Dense(1000, activation='softmax') # 最后要做一个softmax,输出概率归一化 ]) return model if **name** == '**main**': # 主函数,调用 model = generate_vgg16() model.summary() |
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | Using TensorFlow backend. _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 224, 224, 64) 1792 _________________________________________________________________ conv2d_2 (Conv2D) (None, 224, 224, 64) 36928 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 112, 112, 64) 0 _________________________________________________________________ conv2d_3 (Conv2D) (None, 112, 112, 128) 73856 _________________________________________________________________ conv2d_4 (Conv2D) (None, 112, 112, 128) 147584 _________________________________________________________________ max_pooling2d_2 (MaxPooling2 (None, 56, 56, 128) 0 _________________________________________________________________ conv2d_5 (Conv2D) (None, 56, 56, 256) 295168 _________________________________________________________________ conv2d_6 (Conv2D) (None, 56, 56, 256) 590080 _________________________________________________________________ conv2d_7 (Conv2D) (None, 56, 56, 256) 590080 _________________________________________________________________ max_pooling2d_3 (MaxPooling2 (None, 28, 28, 256) 0 _________________________________________________________________ conv2d_8 (Conv2D) (None, 28, 28, 512) 1180160 _________________________________________________________________ conv2d_9 (Conv2D) (None, 28, 28, 512) 2359808 _________________________________________________________________ conv2d_10 (Conv2D) (None, 28, 28, 512) 2359808 _________________________________________________________________ max_pooling2d_4 (MaxPooling2 (None, 14, 14, 512) 0 _________________________________________________________________ conv2d_11 (Conv2D) (None, 14, 14, 512) 2359808 _________________________________________________________________ conv2d_12 (Conv2D) (None, 14, 14, 512) 2359808 _________________________________________________________________ conv2d_13 (Conv2D) (None, 14, 14, 512) 2359808 _________________________________________________________________ max_pooling2d_5 (MaxPooling2 (None, 7, 7, 512) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 25088) 0 _________________________________________________________________ dense_1 (Dense) (None, 4096) 102764544 _________________________________________________________________ dense_2 (Dense) (None, 4096) 16781312 _________________________________________________________________ dense_3 (Dense) (None, 1000) 4097000 ================================================================= Total params: 138,357,544 Trainable params: 138,357,544 Non-trainable params: 0 |
可以看到参数是138,357,544
个,基本可以认为构建无误。
VGG19 构建代码
不妨再模拟一个常用的VGG19
。
VGG19
只是在第 3、4、5 块(Block)各增加一个卷积层。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | from keras.models import Sequential from keras.layers import Dense, Flatten from keras.layers import Conv2D from keras.layers import MaxPooling2D def generate_vgg16(): input_shape = (224, 224, 3) # 输入: 224*244,RGB 三位图 model = Sequential([ Conv2D(64, (3, 3), input_shape=input_shape, padding='same', activation='relu'), # 卷积层,64 个滤波器(卷积核),尺寸 3*3,参数:输入规格,填充,激活函数 Conv2D(64, (3, 3), padding='same', activation='relu'), # 非首层无需指定输入规格 MaxPooling2D(pool_size=(2, 2), strides=(2, 2)), # 2 Conv2D(128, (3, 3), padding='same', activation='relu'), Conv2D(128, (3, 3), padding='same', activation='relu'), MaxPooling2D(pool_size=(2, 2), strides=(2, 2)), # 3 Conv2D(256, (3, 3), padding='same', activation='relu'), Conv2D(256, (3, 3), padding='same', activation='relu'), Conv2D(256, (3, 3), padding='same', activation='relu'), Conv2D(256, (3, 3), padding='same', activation='relu'), MaxPooling2D(pool_size=(2, 2), strides=(2, 2)), # 4 Conv2D(512, (3, 3), padding='same', activation='relu'), Conv2D(512, (3, 3), padding='same', activation='relu'), Conv2D(512, (3, 3), padding='same', activation='relu'), Conv2D(512, (3, 3), padding='same', activation='relu'), MaxPooling2D(pool_size=(2, 2), strides=(2, 2)), # 5 Conv2D(512, (3, 3), padding='same', activation='relu'), Conv2D(512, (3, 3), padding='same', activation='relu'), Conv2D(512, (3, 3), padding='same', activation='relu'), Conv2D(512, (3, 3), padding='same', activation='relu'), MaxPooling2D(pool_size=(2, 2), strides=(2, 2)), # 全连接层 Flatten(), Dense(4096, activation='relu'), Dense(4096, activation='relu'), Dense(1000, activation='softmax') # 最后要做一个softmax,输出概率归一化 ]) return model if **name** == '**main**': # 主函数,调用 model = generate_vgg16() model.summary() |
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | Using TensorFlow backend. _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 224, 224, 64) 1792 _________________________________________________________________ conv2d_2 (Conv2D) (None, 224, 224, 64) 36928 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 112, 112, 64) 0 _________________________________________________________________ conv2d_3 (Conv2D) (None, 112, 112, 128) 73856 _________________________________________________________________ conv2d_4 (Conv2D) (None, 112, 112, 128) 147584 _________________________________________________________________ max_pooling2d_2 (MaxPooling2 (None, 56, 56, 128) 0 _________________________________________________________________ conv2d_5 (Conv2D) (None, 56, 56, 256) 295168 _________________________________________________________________ conv2d_6 (Conv2D) (None, 56, 56, 256) 590080 _________________________________________________________________ conv2d_7 (Conv2D) (None, 56, 56, 256) 590080 _________________________________________________________________ conv2d_8 (Conv2D) (None, 56, 56, 256) 590080 _________________________________________________________________ max_pooling2d_3 (MaxPooling2 (None, 28, 28, 256) 0 _________________________________________________________________ conv2d_9 (Conv2D) (None, 28, 28, 512) 1180160 _________________________________________________________________ conv2d_10 (Conv2D) (None, 28, 28, 512) 2359808 _________________________________________________________________ conv2d_11 (Conv2D) (None, 28, 28, 512) 2359808 _________________________________________________________________ conv2d_12 (Conv2D) (None, 28, 28, 512) 2359808 _________________________________________________________________ max_pooling2d_4 (MaxPooling2 (None, 14, 14, 512) 0 _________________________________________________________________ conv2d_13 (Conv2D) (None, 14, 14, 512) 2359808 _________________________________________________________________ conv2d_14 (Conv2D) (None, 14, 14, 512) 2359808 _________________________________________________________________ conv2d_15 (Conv2D) (None, 14, 14, 512) 2359808 _________________________________________________________________ conv2d_16 (Conv2D) (None, 14, 14, 512) 2359808 _________________________________________________________________ max_pooling2d_5 (MaxPooling2 (None, 7, 7, 512) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 25088) 0 _________________________________________________________________ dense_1 (Dense) (None, 4096) 102764544 _________________________________________________________________ dense_2 (Dense) (None, 4096) 16781312 _________________________________________________________________ dense_3 (Dense) (None, 1000) 4097000 ================================================================= Total params: 143,667,240 Trainable params: 143,667,240 Non-trainable params: 0 |
QA
提交作业时,所有文件都需要上传吗?还是只需要上传修改后的. py 文件?
未回答。(Remain)
是的。
代码中卷积核只指定了窗口的大小 3*3,那么卷积核的过滤函数是怎么确定的呢?
~参数通过通过反向传播训练的~。Check!
图像是 RGB 三位(层)的,为什么不是指定 3 个卷积核形成过滤器(滑动窗口),而是 64、128……?步长是 keras 自行确定的吗?指定的卷积核个数是在图像上默认均匀分布吗?而且 64 个 3*3 窗口根本没法覆盖 224x224 的图像啊。。orz
64 代表卷积核的个数,步长自己设,keras 默认步长应该是 1。卷积通过滑动覆盖整个图片。
RGB 只有 3 个通道,64 个卷积核不应该对应 64 个通道吗,怎么滑动?
64 个卷积核是指单个图像被 64 个过滤器分别过滤了。64 通道是处理后的通道,224 224 3 处理后可以变成 224 224 64。
“处理” 具体对应的是哪一步?什么操作?
处理代表卷积层;通过卷积层之后通道数就变了。
Conv2D 的 filters 和 strides 参数不会互相冲突吗?它们指定的对象有什么区别?
~filters 代表卷积核个数,stride 代表步长,没有冲突~。Check!
多个卷积层堆积到一起究竟有多大意义?我看 padding 都是从输入图像最边缘的一层像素开始的,就算迭代 3 次,外部的 padding 也只能向内传播 3 个像素深度,除了增加算力以外有什么好处吗?我难道不可以直接设置一个具有新的过滤 [复合函数] 的卷积核来完成这样的工作吗?
多个卷积核,每个卷积核的参数都是不一样的,相当于你说的复合函数。
有什么好处?如果相当于复合函数为什么不直接用复合函数?
~是因为神经网络其实本身就是通过不断训练,梯度下降,达到某一个复杂函数的效果~。(Remain)
(深层次)训练效果更好。
我看 VGG 模型的 C 配置里有 conv1-512,能讲讲 1 1 卷积核的作用吗?这里的图像也就三层,为什么要反复用 1 1 卷积呢?类似于一个像素级的激活函数吗?(那么还要 ReLU 干啥呢。。)另外 1 _1 为什么要放在 3_3 卷积核的 [后面]?
1 * 1 卷积核用于改变通道个数,比如从 12 12 256 变成 12 12 512 就可以用 1 * 1 卷积层。
~那不同配置卷积层数不同不影响通道吗?~Check!
池化层到底干了个啥?为啥每次池化了以后通道数翻倍呢?
池化层用于下采样。通道变多是为了组合不同的特征。
我想问的是通道为什么会变多?(不是问 “为了什么而变多”orrrrz)
因为卷积核个数多了,处理代表卷积层;通过卷积层之后通道数就变了。
~卷积可以改变通道数???池化也可以改变通道数?~Check!
我还是不太理解卷积核数大于通道数是如何过滤的?神奇的操作??
嗯嗯,一个卷积核就可以把之前的所有通道都进行一遍卷积操作,输出为 n m 1,x 个卷积层就是 n m x,所以 x 跟之前的层有多少通道没关系。
Task2 特征提取
迁移学习
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | from keras.models import model_from_json from PIL import Image as pil_image from keras import backend as K import numpy as np from pickle import dump from os import listdir import os from keras.models import Model import keras from tqdm import tqdm def load_img_as_np_array(path, target_size): """从给定文件[加载]图像,[缩放]图像大小为给定 target_size,返回[Keras 支持]的浮点数 numpy 数组. # Arguments path: 图像文件路径 target_size: 元组(图像高度, 图像宽度). # Returns numpy 数组. """ img = pil_image.open(path) # 打开文件 img = img.resize(target_size,pil_image.NEAREST) # NEARSET 是一种插值方法 return np.asarray(img, dtype=K.floatx()) #转化为向量 def preprocess_input(x): """预处理图像用于网络输入, 将图像由 RGB 格式转为 BGR 格式. 将图像的每一个图像通道减去其均值 均值 BGR 三个通道的均值分别为 103.939, 116.779, 123.68 # Arguments x: numpy 数组, 4维. data_format: Data format of the image array. # Returns Preprocessed Numpy array. """ # 'RGB'->'BGR', https://www.scivision.co/numpy-image-bgr-to-rgb/ x = x[..., ::-1] mean = [103.939, 116.779, 123.68] x[..., 0] -= mean[0] x[..., 1] -= mean[1] x[..., 2] -= mean[2] return x def load_vgg16_model(): """从当前目录下面的 vgg16_exported.json 和 vgg16_exported.h5 两个文件中导入 VGG16 网络并返回创建的网络模型 vgg16_exported.json 下载链接:链接: https://pan.baidu.com/s/13WQBRb4sr3umP7xbUCxmCg 提取码: ycb5 vgg16_exported.h5 下载链接: https://pan.baidu.com/s/1yF8wybHuzGoTzwSkqTPzzQ 提取码: ub75 注意上传完成的作业时不要上传这两个文件 # Returns 创建的网络模型 model """ json_file = open("vgg16_exported.json","r") loaded_model_json = json_file.read() json_file.close() model = model_from_json(loaded_model_json) model.load_weights("vgg16_exported.h5") return model def extract_features(directory): """提取给定文件夹中所有图像的特征, 将提取的特征保存在文件 features.pkl 中, 提取的特征保存在一个 dict 中, key 为文件名(不带.jpg 后缀), value 为特征值[np.array] Args: directory: 包含jpg文件的文件夹 Returns: None """ model = load_vgg16_model() # 去除模型最后一层 model.layers.pop() model = Model(inputs=model.inputs, outputs=model.layers[-1].output) print("Extracting...") features = dict() pbar = tqdm(total=len(listdir(directory)), desc="进度", ncols=100) for fn in listdir(directory): print("\tRead file:", fn) fn_path = directory + '/' + fn # 返回长、宽、通道的三维张量 arr = load_img_as_np_array(fn_path, target_size=(224,224)) # 改变数组的形态,增加一个维度(批处理)—— 4维 arr = arr.reshape((1, arr.shape[0], arr.shape[1], arr.shape[2])) # 预处理图像为VGG模型的输入 arr = preprocess_input(arr) # 计算特征 feature = model.predict(arr, verbose=0) print("\tprocessed...",end='') id = os.path.splitext(fn)[0] features[id] = feature print("Saved. ", id) pbar.update(1) print("Complete extracting.") return features if **name** == '**main**': # 提取 Flicker8k 数据集中所有图像的特征,保存在一个文件中, 大约一小时的时间,最后的文件大小为 127M # Flickr8k 数据集的下载链接: https://pan.baidu.com/s/1bQcQAz0pxPix9q9kCoZ1aw 提取码: 6gpd # 下载 zip 文件,解压缩到当前目录的子文件夹 Flicker8k_Dataset, 注意上传完成的作业时不要上传这个数据集文件 directory = './Flicker8k_Dataset' features = extract_features(directory) print('提取特征的文件个数:%d' % len(features)) print(keras.backend.image_data_format()) #保存特征到文件 dump(features, open('features.pkl', 'wb')) |
Task3 数据生成
代码
1 2 |
Task4 训练网络
代码
1 2 |
Task5 模型评估
代码
1 2 |
实战项目 2:自动驾驶之交通牌识别
实战项目 3: (开放式)自动驾驶之方向盘操纵
相关文章